package com.situ.mall.product.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.situ.mall.pojo.entity.product.Category;
import com.situ.mall.pojo.entity.product.Product;
import com.situ.mall.product.mapper.CategoryMapper;
import com.situ.mall.product.mapper.ProductMapper;
import com.situ.mall.product.pojo.query.ProductQuery;
import com.situ.mall.product.pojo.vo.ProductVO;
import com.situ.mall.product.service.IProductService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * <p>
 * 商品 服务实现类
 * </p>
 *
 * @author Gao
 * @since 2024-10-09
 */
@Service
public class ProductServiceImpl extends ServiceImpl<ProductMapper, Product> implements IProductService {
    @Autowired
    private ProductMapper productMapper;
    @Autowired
    private CategoryMapper categoryMapper;
    @Autowired
    private RedisTemplate redisTemplate;
    private Map<String, Product> map = new HashMap<String, Product>();
    @Autowired
    private RedissonClient redissonClient;

    @Override
    public IPage<ProductVO> list(ProductQuery productQuery) {
        IPage<ProductVO> page = new Page<>(productQuery.getPage(), productQuery.getLimit());
        page = productMapper.list(page, productQuery);
        return page;
    }

    @Override
    public List<Product> selectPromoProduct(String categoryName) {
        QueryWrapper<Category> categoryQueryWrapper = new QueryWrapper<>();
        categoryQueryWrapper.eq("name", categoryName);
        Category category = categoryMapper.selectOne(categoryQueryWrapper);

        QueryWrapper<Product> productQueryWrapper = new QueryWrapper<>();
        productQueryWrapper.eq("category3_id", category.getId())
                .orderByDesc("sales")
                .last("limit 7");

        return productMapper.selectList(productQueryWrapper);
    }


    //@Override
    public Product selectById1(Long id) {
        System.out.println("ProductServiceImpl.selectById");
        //1.先去缓存中取
        Product product = map.get("product_" + id);
        //2.缓存中没有
        if (product == null) {
            //查找数据库
            product = productMapper.selectById(id);
            //同步到缓存
            map.put("product_" + id, product);
        }
        return product;
    }



    //asynchronize   synchronized 同步，加锁
    //public synchronized Product selectById(Long id) {}
    //锁住整个方法，可以解决单机下面缓存击穿问题，但是引起严重问题：
    //有问题的情况其实只有redis缓存没有数据的时候才需要加锁，但是因为加到方法上，
    //后面redis中已经建立缓存product已经有值，可以直接从redis中查找出来，没有缓存击穿问题，
    //但是每次还是只能有一个线程访问，并发度就会大大降低
    //@Override
    public Product selectById2(Long id) {
        System.out.println("ProductServiceImpl.selectById");
        //1.先去缓存中取
        Product product = (Product) redisTemplate.opsForValue().get("product_" + id);
        //2.缓存中没有
        if (product == null) {
            //多个线程同时进了if，但是只有一个线程拿到锁查询数据库赋值给product，
            //product这个时候已经不是空了，但是外面的这个if没有拦截住其他线程，
            //造成其他线程也会进synchronized查询数据库
            synchronized (this) {
                //去缓存里面再次确认是不是已经有了
                product = (Product) redisTemplate.opsForValue().get("product_" + id);
                if (product == null) {//双重检查
                    product = productMapper.selectById(id);
                    if (product == null) {
                        //不存在id恶意攻击，缓存空数据，同时设置过期时间，解决缓存穿透问题
                        redisTemplate.opsForValue().set("product_" + id, new Product(), 1, TimeUnit.MINUTES);
                    } else {
                        //真实的缓存数据设置随机过期时间，解决缓存雪崩问题
                        redisTemplate.opsForValue().set("product_" + id, product, 1, TimeUnit.DAYS);
                    }
                }
            }
        }
        return product;
    }

    ///@Override
    public Product selectById3(Long id) {
        System.out.println("ProductServiceImpl.selectById");
        //1.先去缓存中取
        Product product = (Product) redisTemplate.opsForValue().get("product_" + id);
        //2.缓存中没有
        if (product == null) {
            //多个线程同时进了if，但是只有一个线程拿到锁查询数据库赋值给product，
            //product这个时候已经不是空了，但是外面的这个if没有拦截住其他线程，
            //造成其他线程也会进synchronized查询数据库
            redissonClient.getLock("product_lock_" + id).lock();
            try {
                //去缓存里面再次确认是不是已经有了
                product = (Product) redisTemplate.opsForValue().get("product_" + id);
                if (product == null) {//双重检查
                    product = productMapper.selectById(id);
                    if (product == null) {
                        //不存在id恶意攻击，缓存空数据，同时设置过期时间，解决缓存穿透问题
                        redisTemplate.opsForValue().set("product_" + id, new Product(), 1, TimeUnit.MINUTES);
                    } else {
                        //真实的缓存数据设置随机过期时间，解决缓存雪崩问题
                        redisTemplate.opsForValue().set("product_" + id, product, 1, TimeUnit.DAYS);
                    }
                }
            } finally {
                redissonClient.getLock("product_lock_" + id).unlock();
            }
        }
        return product;
    }

    // 解决缓存击穿问题加锁： sync = true
    @Cacheable(value = "product", key = "#root.methodName + ':' + #id", sync = true)
    @Override
    public Product selectById(Long id) {
        return productMapper.selectById(id);
    }

    @CacheEvict(value = "product", key = "'selectById:' + #product.id")
    @Override
    public void update(Product product) {
        productMapper.updateById(product);
    }
}
